/* -*-mode:java; c-basic-offset:2; -*- */
/* JCTerm
 * Copyright (C) 2002,2007 ymnk, JCraft,Inc.
 *  
 * Written by: ymnk<ymnk@jcaft.com>
 *   
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
   
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package net.sshiffon.jctermish;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

import com.jcraft.jcterm.Connection;
import com.jcraft.jcterm.Emulator;
import com.jcraft.jcterm.EmulatorVT100;
import com.jcraft.jcterm.Splash;
import com.jcraft.jcterm.Term;

public class SshiffonTerm extends JPanel implements KeyListener, /*Runnable,*/
        ClipboardOwner,
    Term {
	private static final long serialVersionUID = 1230694686796329842L;
OutputStream out;
  InputStream in;
  Emulator emulator=null;

  Connection connection=null;

	Dimension charSize = new Dimension();
  private BufferedImage img;
  private BufferedImage background;
  private Graphics2D cursor_graphics;
  private Graphics2D graphics;
  private java.awt.Color defaultbground=Color.white;
  private java.awt.Color defaultfground=Color.black;
  private java.awt.Color bground=Color.white;
  private java.awt.Color fground=Color.black;
  private java.awt.Component term_area=null;
  private java.awt.Font font;

  private boolean bold=false;
  private boolean underline=false;
  private boolean reverse=false;

  private int term_width=80;
  private int term_height=24;

  private int descent=0;

  private int x=0;
  private int y=0;

  private int char_width;
  private int char_height;

	private Font normalFont;
  private int line_space=0;
  //private int line_space=-2;
  @SuppressWarnings("unused")
private int compression=0;

  private boolean antialiasing=false;

  private Splash splash=null;


	protected Point selectionStart;

	protected Point selectionEnd;

	protected boolean selectionInProgress;
  protected int clientScrollOrigin;

  private Clipboard clipboard;

  private final Object[] colors= {Color.black, Color.red, Color.green,
      Color.yellow, Color.blue, Color.magenta, Color.cyan, Color.white};


//  private FocusListener focusListener = new DumpingFocusListener();


	private void establishFontMetrics() {
		final BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
		final Graphics2D graphics = img.createGraphics();
		graphics.setFont(normalFont);

		final FontMetrics fo = graphics.getFontMetrics();
		descent = fo.getDescent();
		charSize.width = fo.charWidth('@');
		charSize.height = fo.getHeight() + line_space * 2;
		descent += line_space;

		img.flush();
		graphics.dispose();
	}
  public void copy()
  {

		if (selectionStart == null || selectionEnd == null)
			return;

		Point top;
		Point bottom;

		if (selectionStart.y == selectionEnd.y) {
			/* same line */
			top = selectionStart.x < selectionEnd.x ? selectionStart
					: selectionEnd;
			bottom = selectionStart.x >= selectionEnd.x ? selectionStart
					: selectionEnd;
		} else {
			top = selectionStart.y < selectionEnd.y ? selectionStart
					: selectionEnd;
			bottom = selectionStart.y > selectionEnd.y ? selectionStart
					: selectionEnd;
		}

		final StringBuffer selection = new StringBuffer();
		if( top.y < 0 ){
			final Point scrollEnd = bottom.y >= 0 ? new Point(term_width, -1) : bottom;
			grabSelectedText(top.y, scrollEnd.y - top.y,selection, top, scrollEnd);

		}

		if( bottom.y >= 0 ){
			final Point backBegin = top.y < 0 ? new Point(0, 0) : top;
			grabSelectedText( 0, backBegin.y, term_width, bottom.y -  backBegin.y + 1,
					selection, backBegin, bottom);
		}


		if(selection.length() == 0) return;

		try {
			clipboard.setContents(new StringSelection(selection.toString()), this);
		} catch (final IllegalStateException e) {
			e.printStackTrace();
		}
  }

  public void grabSelectedText(int firstLine, int height, StringBuffer selection, Point top, Point scrollEnd)
  {

		@SuppressWarnings("unused")
		final int lastLine = firstLine + height;
  }
  public void grabSelectedText(int x, int y, int width, int height,
          StringBuffer selection, Point top, Point bottom)
  {

		final int startRow = y;
		final int endRow = y + height;
		final int startCol = x;
		final int endCol = x + width;

                	for(int row = startRow ; row < endRow ; row++ ){

				@SuppressWarnings("unused")
				int beginRun = startCol;
				for(int col = startCol; col < endCol; col++ ){
					final int location = row * width + col;
					if(location < 0) { // || location > getWidth() ){
						System.err.println("Requested out of bounds runs:" + "x:" +x +" y:" +y +" w:" +width + " h:" +height);
						continue;
					}
                   //                     consumeRun(top,bottom, beginRun, row, width, height, buf, row * width + beginRun, col - beginRun);
					beginRun = col;
					
				}
				//end row
			//	if(lastStyle == null)
			//		logger.error("Style is null for run supposed to be from "+ beginRun + " to " + endCol + "on row " + row);
			//	else
					//consumer.consumeRun(beginRun, row, lastStyle, buf, row * width + beginRun, endCol - beginRun);

			}

  }

 public boolean consumeRun(Boolean first, final StringBuffer selection, final Point begin, final Point end, final int x, final int y, final int w, final int h, final char[] buf, final int start, final int len)
 {
		int startPos = start;
		int extent = len;

		if(y == end.y){
			extent = Math.min(end.x  - x, extent);

		}
		if(y == begin.y ){
			final int xAdj = Math.max(0, begin.x - x);
			startPos += xAdj;
			extent -= xAdj;
			if( extent < 0) return first;
		}
		if(extent < 0) return first; // The run is off the left edge of the selection on the first line,
							   //  or off the right edge on the last line.
		if(len > 0){
			if(!first && x == 0) selection.append('\n');
                        
			first = false;
			if( startPos < 0 ){
				System.err.println("Attempt to copy to selection from before start of buffer");
			}else if (startPos + extent >= buf.length){
				System.err.println("Attempt to copy to selection from after end of buffer");
			}else{
				selection.append(buf,startPos, extent);
			}
		}
                return first;
  }
  
  public void paste()
  {

    try {
        final String selection = (String) clipboard.getData( DataFlavor.stringFlavor );

        if(selection !=null)
        {
            System.out.println("pasting: "+selection);
            putString(selection);
        }
    } catch (final UnsupportedFlavorException e) {
        e.printStackTrace();
    } catch (final IOException e) {
        e.printStackTrace();
    }
  }

  public SshiffonTerm(){

    enableEvents(AWTEvent.KEY_EVENT_MASK);
    addKeyListener(this);
    //addFocusListener(focusListener);
    setFont();

    background=new BufferedImage(char_width, char_height,
        BufferedImage.TYPE_INT_RGB);
    {
      Graphics2D foog=(Graphics2D)(background.getGraphics());
      foog.setColor(getBackGround());
      foog.fillRect(0, 0, char_width, char_height);
      foog.dispose();
    }

    setSize(getTermWidth(), getTermHeight());

    if(splash!=null)
      splash.draw(img, getTermWidth(), getTermHeight());
    else
      clear();

    term_area=this;

    setPreferredSize(new Dimension(getTermWidth(), getTermHeight()));

    setSize(getTermWidth(), getTermHeight());
    setFocusable(true);
    enableInputMethods(true);
    setRequestFocusEnabled(true);

    setFocusTraversalKeysEnabled(false);

    establishFontMetrics();
    setUpClipboard();
    setUpMouseListeners();
  }

	private Point panelToCharCoords(final Point p) {
		return new Point(p.x / charSize.width , p.y / charSize.height + clientScrollOrigin);
	}
  private void setUpMouseListeners()
  {

		addMouseMotionListener(new MouseMotionAdapter() {
			@Override
			public void mouseDragged(final MouseEvent e) {
				final Point charCoords = panelToCharCoords(e.getPoint());

				if (!selectionInProgress) {
					selectionStart = new Point(charCoords);
					selectionInProgress = true;
				}
				repaint();
				selectionEnd = charCoords;
				selectionEnd.x = Math.min(selectionEnd.x + 1, term_width );

			}
		});
                
                
                final JPopupMenu contextMenu = new JPopupMenu("Edit");
                JMenuItem copyItem = new JMenuItem("Copy");
                copyItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { copy(); } });
                contextMenu.add(copyItem);
                JMenuItem pasteItem = new JMenuItem("Paste");
                pasteItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { paste(); } });
                contextMenu.add(pasteItem);

                setComponentPopupMenu(contextMenu);

		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(final MouseEvent e) {
				selectionInProgress = false;
//				if ( selectionStart != null && selectionEnd != null)
//					copySelection(selectionStart, selectionEnd);
				repaint();
			}

			@Override
			public void mouseClicked(final MouseEvent e) {
				requestFocusInWindow();
				selectionStart = null;
				selectionEnd = null;
//				if(e.getButton() == MouseEvent.BUTTON3 )
//					paste();
				repaint();
			}
		});
  }

  private void setUpClipboard() {
		clipboard = Toolkit.getDefaultToolkit().getSystemSelection();
		if (clipboard == null)
			clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
  }
  

  private void setFont()
  {
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    String[] fontNames = ge.getAvailableFontFamilyNames();

    if(Arrays.binarySearch(fontNames, "Lucida Console")>-1) {
        setFont("Lucida Console-14");
    } else {
        setFont("Monospaced-14");
    }

  }

  private void setFont(String fname){
    font=java.awt.Font.decode(fname);
    BufferedImage img=new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics=(Graphics2D)(img.getGraphics());
    graphics.setFont(font);
    {
      FontMetrics fo=graphics.getFontMetrics();
      descent=fo.getDescent();
      /*
      System.out.println(fo.getDescent());
      System.out.println(fo.getAscent());
      System.out.println(fo.getLeading());
      System.out.println(fo.getHeight());
      System.out.println(fo.getMaxAscent());
      System.out.println(fo.getMaxDescent());
      System.out.println(fo.getMaxDecent());
      System.out.println(fo.getMaxAdvance());
      */
      char_width=(int)(fo.charWidth((char)'@'));
      char_height=(int)(fo.getHeight())+(line_space*2);
      descent+=line_space;
    }
    img.flush();
    graphics.dispose();
  }

  public void setSize(int w, int h){

    super.setSize(w, h);
    BufferedImage imgOrg=img;
    if(graphics!=null)
      graphics.dispose();

    int column=w/getCharWidth();
    int row=h/getCharHeight();
    term_width=column;
    term_height=row;

    if(emulator!=null)
      emulator.reset();

    img=new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    graphics=(Graphics2D)(img.getGraphics());
    graphics.setFont(font);

    clear_area(0, 0, w, h);

    if(imgOrg!=null){
      Shape clip=graphics.getClip();
      graphics.setClip(0, 0, getTermWidth(), getTermHeight());
      graphics.drawImage(imgOrg, 0, 0, term_area);
      graphics.setClip(clip);
    }

    if(cursor_graphics!=null)
      cursor_graphics.dispose();

    cursor_graphics=(Graphics2D)(img.getGraphics());
    cursor_graphics.setColor(getForeGround());
    cursor_graphics.setXORMode(getBackGround());

    setAntiAliasing(antialiasing);

    if(connection!=null){
      connection.requestResize(this);
    }

    if(imgOrg!=null){
      imgOrg.flush();
      imgOrg=null;
    }
  }

  public void start(Connection connection){
    this.connection=connection;
    in=connection.getInputStream();
    out=connection.getOutputStream();
    emulator=new EmulatorVT100(this, in);
    
    emulator.reset();
    //System.out.println("emulator starting");
    emulator.start();
    //System.out.println("emulator started");
    if(splash!=null)
      splash.draw(img, getTermWidth(), getTermHeight());
    else
      clear();
    //System.out.println("hi");
    redraw(0, 0, getTermWidth(), getTermHeight());
  }

  public void paintComponent(Graphics g){
    super.paintComponent(g);
    if(img!=null){
      g.drawImage(img, 0, 0, term_area);
		Graphics2D gfx = (Graphics2D) g;
			drawSelection(gfx);
    }
  }


	public void drawSelection(Graphics2D g) {
		/* which is the top one */
		Point top;
		Point bottom;
                g.setColor(fground);
		g.setXORMode(bground);

		if (selectionStart == null || selectionEnd == null)
			return;

		if (selectionStart.y == selectionEnd.y) {
			/* same line */
			if (selectionStart.x == selectionEnd.x)
				return;
			top = selectionStart.x < selectionEnd.x ? selectionStart
					: selectionEnd;
			bottom = selectionStart.x >= selectionEnd.x ? selectionStart
					: selectionEnd;

			g.fillRect(top.x * charSize.width, (top.y - clientScrollOrigin) * charSize.height,
					(bottom.x - top.x) * charSize.width, charSize.height);

		} else {
			top = selectionStart.y < selectionEnd.y ? selectionStart
					: selectionEnd;
			bottom = selectionStart.y > selectionEnd.y ? selectionStart
					: selectionEnd;
			/* to end of first line */
			g.fillRect(top.x * charSize.width, (top.y - clientScrollOrigin) * charSize.height,
					(term_width - top.x) * charSize.width, charSize.height);

			if (bottom.y - top.y > 1) {
				/* intermediate lines */
				g.fillRect(0, (top.y + 1 - clientScrollOrigin) * charSize.height,
						term_width * charSize.width, (bottom.y - top.y - 1)
								* charSize.height);
			}

			/* from beginning of last line */

			g.fillRect(0, (bottom.y  - clientScrollOrigin) * charSize.height, bottom.x
					* charSize.width, charSize.height);
		}
	}

  public void paint(Graphics g){
    super.paint(g);
  }

  public void processKeyEvent(KeyEvent e){
    //System.out.println(e);
    int id=e.getID();
    if(id==KeyEvent.KEY_PRESSED){
      keyPressed(e);
    }
    else if(id==KeyEvent.KEY_RELEASED){ 
      /*keyReleased(e);*/
    }
    else if(id==KeyEvent.KEY_TYPED){
      keyTyped(e);/*keyTyped(e);*/
    }
    e.consume(); // ??
  }

  byte[] obuffer=new byte[3];

  public void keyPressed(KeyEvent e){
    int keycode=e.getKeyCode();
    byte[] code=null;

    switch(keycode){
      case KeyEvent.VK_CONTROL:
      case KeyEvent.VK_SHIFT:
      case KeyEvent.VK_ALT:
      case KeyEvent.VK_CAPS_LOCK:
        return;
      case KeyEvent.VK_ENTER:
        code=emulator.getCodeENTER();
        break;
      case KeyEvent.VK_UP:
        code=emulator.getCodeUP();
        break;
      case KeyEvent.VK_DOWN:
        code=emulator.getCodeDOWN();
        break;
      case KeyEvent.VK_RIGHT:
        code=emulator.getCodeRIGHT();
        break;
      case KeyEvent.VK_LEFT:
        code=emulator.getCodeLEFT();
        break;
      case KeyEvent.VK_F1:
        code=emulator.getCodeF1();
        break;
      case KeyEvent.VK_F2:
        code=emulator.getCodeF2();
        break;
      case KeyEvent.VK_F3:
        code=emulator.getCodeF3();
        break;
      case KeyEvent.VK_F4:
        code=emulator.getCodeF4();
        break;
      case KeyEvent.VK_F5:
        code=emulator.getCodeF5();
        break;
      case KeyEvent.VK_F6:
        code=emulator.getCodeF6();
        break;
      case KeyEvent.VK_F7:
        code=emulator.getCodeF7();
        break;
      case KeyEvent.VK_F8:
        code=emulator.getCodeF8();
        break;
      case KeyEvent.VK_F9:
        code=emulator.getCodeF9();
        break;
      case KeyEvent.VK_F10:
        code=emulator.getCodeF10();
        break;
      case KeyEvent.VK_TAB:
        code=emulator.getCodeTAB();
        break;
    }
    if(code!=null){
      try{
        out.write(code, 0, code.length);
        out.flush();
      }
      catch(Exception ee){
      }
      return;
    }

    char keychar=e.getKeyChar();
    if((keychar&0xff00)==0){
      obuffer[0]=(byte)(e.getKeyChar());
      try{
        out.write(obuffer, 0, 1);
        out.flush();
      }
      catch(Exception ee){
      }
    }
  }

  public void putString(String buffer)
  {

    //if((keychar&0xff00)==0){
//      obuffer[0]= keychar;
      try{
        out.write(buffer.getBytes());
        out.flush();
      }
      catch(Exception ee){
          ee.printStackTrace();
      }
    //}
  }


  public void keyTyped(KeyEvent e){
    char keychar=e.getKeyChar();
    if((keychar&0xff00)!=0){
      char[] foo=new char[1];
      foo[0]=keychar;
      try{
        byte[] goo=new String(foo).getBytes("EUC-JP");
        out.write(goo, 0, goo.length);
        out.flush();
      }
      catch(Exception ee){
          ee.printStackTrace();
      }
    }
  }

  public int getTermWidth(){
    return char_width*term_width;
  }

  public int getTermHeight(){
    return char_height*term_height;
  }

  public int getCharWidth(){
    return char_width;
  }

  public int getCharHeight(){
    return char_height;
  }

  public int getColumnCount(){
    return term_width;
  }

  public int getRowCount(){
    return term_height;
  }

  public void clear(){
    graphics.setColor(getBackGround());
    graphics.fillRect(0, 0, char_width*term_width, char_height*term_height);
    graphics.setColor(getForeGround());
  }

  public void setCursor(int x, int y){
    //System.out.println("setCursor: "+x+","+y); 
    this.x=x;
    this.y=y;
  }

  public void draw_cursor(){
    cursor_graphics.fillRect(x, y-char_height, char_width, char_height);
    repaint(x, y-char_height, char_width, char_height);
  }

  public void redraw(int x, int y, int width, int height){
    repaint(x, y, width, height);
  }

  public void clear_area(int x1, int y1, int x2, int y2){
    //System.out.println("clear_area: "+x1+" "+y1+" "+x2+" "+y2);
    graphics.setColor(getBackGround());
    graphics.fillRect(x1, y1, x2-x1, y2-y1);
    graphics.setColor(getForeGround());
  }

  public void scroll_area(int x, int y, int w, int h, int dx, int dy){
    //System.out.println("scroll_area: "+x+" "+y+" "+w+" "+h+" "+dx+" "+dy);
    graphics.copyArea(x, y, w, h, dx, dy);
    repaint(x+dx, y+dy, w, h);
  }

  public void drawBytes(byte[] buf, int s, int len, int x, int y){
    //    clear_area(x, y, x+len*char_width, y+char_height);
    //    graphics.setColor(getForeGround());

    //System.out.println("drawString: "+x+","+y+" "+len+" "+new String(buf, s, len));

    graphics.drawBytes(buf, s, len, x, y-descent);
    if(bold)
      graphics.drawBytes(buf, s, len, x+1, y-descent);

    if(underline){
      graphics.drawLine(x, y-1, x+len*char_width, y-1);
    }

  }

  public void drawString(String str, int x, int y){
    //    clear_area(x, y, x+str.length()*char_width, y+char_height);
    //    graphics.setColor(getForeGround());
    graphics.drawString(str, x, y-descent);
    if(bold)
      graphics.drawString(str, x+1, y-descent);

    if(underline){
      graphics.drawLine(x, y-1, x+str.length()*char_width, y-1);
    }

  }

  public void beep(){
    Toolkit.getDefaultToolkit().beep();
  }

  /** Ignores key released events. */
  public void keyReleased(KeyEvent event){
  }

  //  public void keyPressed(KeyEvent event){}

  public void setSplash(Splash foo){
    this.splash=foo;
  }

  public void setLineSpace(int foo){
    this.line_space=foo;
  }

  public boolean getAntiAliasing(){
    return antialiasing;
  }

  public void setAntiAliasing(boolean foo){
    if(graphics==null)
      return;
    antialiasing=foo;
    java.lang.Object mode=foo ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON
        : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
    RenderingHints hints=new RenderingHints(
        RenderingHints.KEY_TEXT_ANTIALIASING, mode);
    graphics.setRenderingHints(hints);
  }

  public void setCompression(int compression){
    if(compression<0||9<compression)
      return;
    this.compression=compression;
  }

  private java.awt.Color toColor(Object o){
    if(o instanceof String){
      return java.awt.Color.getColor((String)o);
    }
    if(o instanceof java.awt.Color){
      return (java.awt.Color)o;
    }
    return Color.white;
  }

  public void setDefaultForeGround(Object f){
    defaultfground=toColor(f);
  }

  public void setDefaultBackGround(Object f){
    defaultbground=toColor(f);
  }

  public void setForeGround(Object f){
    fground=toColor(f);
    graphics.setColor(getForeGround());
  }

  public void setBackGround(Object b){
    bground=toColor(b);
    Graphics2D foog=(Graphics2D)(background.getGraphics());
    foog.setColor(getBackGround());
    foog.fillRect(0, 0, char_width, char_height);
    foog.dispose();
  }

  private java.awt.Color getForeGround(){
    if(reverse)
      return bground;
    return fground;
  }

  private java.awt.Color getBackGround(){
    if(reverse)
      return fground;
    return bground;
  }

  public Object getColor(int index){
    if(colors==null||index<0||colors.length<=index)
      return null;
    return colors[index];
  }

  public void setBold(){
    bold=true;
  }

  public void setUnderline(){
    underline=true;
  }

  public void setReverse(){
    reverse=true;
    if(graphics!=null)
      graphics.setColor(getForeGround());
  }

  public void resetAllAttributes(){
    bold=false;
    underline=false;
    reverse=false;
    bground=defaultbground;
    fground=defaultfground;
    if(graphics!=null)
      graphics.setColor(getForeGround());
  }

    public void lostOwnership(Clipboard clipboard, Transferable contents) {
        throw new UnsupportedOperationException("Not supported yet.");
    }




}
